﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Minestorm.Evolved.IoC
{
	public class TypeMap
	{
		private Dictionary<Type, ConcreteType> _dictionary = new Dictionary<Type, ConcreteType>();
		private ConstructionMapFactory _constructionMapFactory;
		private SingletonCache _singletonCache;

		public TypeMap(ConstructionMapFactory constructionMapFactory, SingletonCache singletonCache)
		{
			_constructionMapFactory = constructionMapFactory;
			_singletonCache = singletonCache;
		}

		public void AddConcreteTypeMap<I, C>()
			where I : class
			where C : class, I
		{
			Add<I, C>(false);
		}

		public void AddSingletonTypeMap<I, C>()
			where I : class
			where C : class, I
		{
			Add<I, C>(true);
		}

		private void Add<I, C>(Boolean singleton)
			where I : class
			where C : class, I
		{
			ConcreteType existing;

			if (_dictionary.TryGetValue(typeof(I), out existing))
			{
				if (existing.Singleton)
					_singletonCache.RemoveInstance<I>();

				_dictionary.Remove(typeof(I));
			}

			_dictionary.Add(typeof(I), new ConcreteType()
			{
				Type = typeof(C),
				Singleton = singleton,
				ConstructionMap = _constructionMapFactory.CreateFromConcreteType(typeof(C))
			});
		}

		public ConcreteType GetConcreteTypeFor<I>()
			where I : class
		{
			return GetConcreteTypeFor(typeof(I));
		}

		public ConcreteType GetConcreteTypeFor(Type interfaceType)
		{
			ConcreteType concreteType;

			_dictionary.TryGetValue(interfaceType, out concreteType);

			return concreteType;
		}

		public Boolean ContainsInterfaceType<I>()
			where I : class
		{
			return ContainsInterfaceType(typeof(I));
		}

		public Boolean ContainsInterfaceType(Type type)
		{
			if (_dictionary.ContainsKey(type))
				return true;

			return TryRegisterDefaultImplementation(type);
		}

		public void Clear()
		{
			_dictionary.Clear();
		}

		private Boolean TryRegisterDefaultImplementation(Type type)
		{
			var att = type.GetSingleAttribute<DefaultImplementationAttribute>(false);

			if (att == null)
				return false;

			try
			{
				if (att.Singleton)
					GetType().GetGenericMethod("AddSingletonTypeMap", BindingFlags.Public | BindingFlags.Instance, type, att.Type).Invoke(this, null);
				else
					GetType().GetGenericMethod("AddConcreteTypeMap", BindingFlags.Public | BindingFlags.Instance, type, att.Type).Invoke(this, null);
			}
			catch (TargetInvocationException tie)
			{
				throw tie.InnerException;
			}

			return true;
		}
	}
}
